﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Resources;
using log4net;
using ResxCrunch.Utils;

namespace ResxCrunch.Business.Managers
{
    /// <summary>
    /// Resource handling
    /// </summary>
    public class ResourceMngr
    {
        #region Constants
        /// <summary>
        /// Logger declaration
        /// </summary>
        private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Instance of the ResourceMngr class. Lazy initialization
        /// </summary>
        private static readonly ResourceMngr instance = new ResourceMngr();

        /// <summary>
        /// Extension of the files the manager works with
        /// </summary>
        private const string FileExtension = "resources";
        #endregion Constants

        #region Attributes
        #endregion Attributes

        #region Initialization
        /// <summary>
        /// Prevents a default instance of the ResourceMngr class from being created.
        /// </summary>
        private ResourceMngr()
        {
        }

        /// <summary>
        /// Gets the reference to the object here.
        /// </summary>
        public static ResourceMngr Instance
        {
            get
            {
                return instance;
            }
        }
        #endregion Initialization

        #region Public Methods
        /// <summary>
        /// Loads the different cultures that belong to the files in the selected dir
        /// </summary>
        /// <param name="path">Directory that can contain files with different cultures</param>
        /// <param name="recursive">Should subdirs be searched too?</param>
        /// <returns>List of different cultures, including default culture (string.Empty) too</returns>
        public List<string> GetDifferentCultures(string path, bool recursive)
        {
            return this.GetDifferentCultures(path, string.Format("*.{0}", FileExtension), recursive);
        }

        /// <summary>
        /// Loads the different cultures that belong to the files in the selected dir
        /// </summary>
        /// <param name="path">Directory that can contain files with different cultures</param>
        /// <param name="mask">The mask of the loaded files</param>
        /// <param name="recursive">Should subdirs be searched too?</param>
        /// <returns>
        /// List of different cultures, including default culture (string.Empty) too     
        /// Returns null if file list could not be obtained
        /// </returns>
        public List<string> GetDifferentCultures(string path, string mask, bool recursive)
        {
            List<string> fileList = FileMngr.Instance.GetFileList(mask, path, recursive);

            List<string> cultures = new List<string>();

            if (fileList == null)
            {
                log.WarnFormat("Error loading file list from '{0}', returning null as resource data.", path);
                return null;
            }

            string culture;

            // Get the different cultures
            foreach (string fileName in fileList)
            {
                culture =ResxMngr.Instance.ExtractCultureFromFileName(fileName);

                if (!cultures.Contains(culture))
                {
                    cultures.Add(culture);
                }
            }

            return cultures;
        }

        /// <summary>
        /// Extracts the culture info from the resx file
        /// </summary>
        /// <param name="path">The full path and filename to the resx file</param>
        /// <returns>Culture info of the extracted culture or false if the culture is not valid</returns>
        public CultureInfo GetResourceCulture(string path)
        {
            // Remove the extension and return the string
            string fname = FileMngr.Instance.ExtractNameFromFilename(FileMngr.Instance.ExtractFileName(path));
            System.Globalization.CultureInfo ci = null;

            string cult = String.Empty;
            if (fname.IndexOf(".") != -1)
            {
                cult = fname.Substring(fname.LastIndexOf('.') + 1);
            }

            if (cult != String.Empty)
            {
                try
                {
                    ci = new System.Globalization.CultureInfo(cult);
                }
                catch (Exception)
                {
                    //log.WarnFormat("Invalid culture: {0}", cult);
                }
            }

            return ci;
        }

        /// <summary>
        /// Decides if resx file is culture specific (name contains culture like file.de.resx)
        /// </summary>
        /// <param name="path">The full path and filename to the resx file</param>
        /// <returns>True if culture specific</returns>
        public bool ResourceIsCultureSpecific(string path)
        {
            if (this.GetResourceCulture(path) != null)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Loads the content of resx files into a datatable
        /// </summary>
        /// <param name="path">Directory where the resx files are located</param>
        /// <param name="recursive">Subdirs should be searched too</param>
        /// <returns>Datatable filled with data from the files. Null if there was an error</returns>
        public DataTable LoadAllResourceData(string path, bool recursive)
        {
            List<string> cultures = this.GetDifferentCultures(path, recursive);
            List<string> fileList = FileMngr.Instance.GetFileList(string.Format("*.{0}", FileExtension), path, recursive);

            if (fileList == null)
            {
                log.WarnFormat("Error loading file list from '{0}', returning null as resource data.", path);
                return null;
            }

            DataTable table = new DataTable("Data");

            table.Columns.Add(Enums.DataColumn.Nr, typeof(int));
            table.Columns.Add(Enums.DataColumn.Directory);
            table.Columns.Add(Enums.DataColumn.File);
            table.Columns.Add(Enums.DataColumn.Key);
            table.Columns.Add(Enums.DataColumn.DefaultValue);

            foreach (string c in cultures)
            {
                if (c.Length > 0)
                {
                    table.Columns.Add(c);
                }
            }

            int counter = 1;
            foreach (string file in fileList)
            {
                if (!this.ResourceIsCultureSpecific(file))
                {
                    DataTable resourceTable = this.LoadResourceData(file, path, null, ref counter, cultures);

                    foreach (DataRow dr in resourceTable.Rows)
                    {
                        table.ImportRow(dr);
                    }
                }
            }

            return table;
        }

        /// <summary>
        /// Loads the data of a resx file into a datatable
        /// </summary>
        /// <param name="file">Full path to the resx file</param>
        /// <returns>
        /// Datatable containing data loaded from the resx file
        /// </returns>
        public DataTable LoadResourceData(string file)
        {
            string baseDir = FileMngr.Instance.ExtractFileDir(file);
            List<string> cultures = this.GetDifferentCultures(FileMngr.Instance.ExtractFileDir(file), string.Concat(FileMngr.Instance.ExtractNameFromFilename(file), string.Format(".*.{0}", FileExtension)), false);

            int counter = 1;
            return this.LoadResourceData(file, baseDir, null, ref counter, cultures);
        }

        /// <summary>
        /// Loads the data of a resx file into a datatable
        /// </summary>
        /// <param name="file">Full path to the resx file</param>
        /// <param name="basePath">The base path to the resx files</param>
        /// <param name="excludeList">List of file names that need to be excluded</param>
        /// <param name="counter">The counter.</param>
        /// <param name="cultures">List of different cultures</param>
        /// <returns>
        /// Datatable containing data loaded from the resx file
        /// </returns>
        public DataTable LoadResourceData(string file, string basePath, string[] excludeList, ref int counter, List<string> cultures)
        {
            DataTable resTable = new DataTable("Data");
            resTable.Columns.Add(Enums.DataColumn.Nr, typeof(int));
            resTable.Columns.Add(Enums.DataColumn.Directory);
            resTable.Columns.Add(Enums.DataColumn.File);
            resTable.Columns.Add(Enums.DataColumn.Key);
            resTable.Columns.Add(Enums.DataColumn.DefaultValue);

            Dictionary<string, ResourceReader> readerList = new Dictionary<string, ResourceReader>();

            foreach (string c in cultures)
            {
                if (c.Length > 0)
                {
                    resTable.Columns.Add(c);

                    // Open all resource readers to different cultures
                    string cultureFileName = string.Concat(FileMngr.Instance.ExtractNameFromFilename(FileMngr.Instance.ExtractFileName(file)), ".", c, FileMngr.Instance.ExtractFileExtension(file));
                    string cultureFilePath = Path.Combine(FileMngr.Instance.ExtractFileDir(file), cultureFileName);

                    if (File.Exists(cultureFilePath))
                    {
                        try
                        {
                            ResourceReader rxrr = new ResourceReader(cultureFilePath);
                            ////FileInfo fi = new FileInfo(cultureFilePath);
                            readerList.Add(c, rxrr);
                        }
                        catch (Exception ex)
                        {
                            log.Warn("Could not open resource reader.", ex);
                        }
                    }
                }
            }

            if (File.Exists(file))
            {
                using (ResourceReader reader = new ResourceReader(file))
                {
                    FileInfo fi = new FileInfo(file);
                    string fileName = FileMngr.Instance.ExtractNameFromFilename(fi.Name);
                    string relativePath = FileMngr.Instance.RelativeDir(basePath, fi.DirectoryName);

                    try
                    {
                        ////IDictionaryEnumerator ide = reader.GetEnumerator();

                        #region Read data
                        foreach (DictionaryEntry de in reader)
                        {
                            if (de.Value is string)
                            {
                                string key = (string)de.Key;

                                bool exclude = false;
                                if (excludeList != null)
                                {
                                    foreach (string e in excludeList)
                                    {
                                        if (key.EndsWith(e))
                                        {
                                            exclude = true;
                                            break;
                                        }
                                    }
                                }

                                if (!exclude)
                                {
                                    string value = de.Value.ToString();

                                    DataRow dr = resTable.NewRow();

                                    dr[Enums.DataColumn.Nr] = counter;
                                    dr[Enums.DataColumn.Directory] = relativePath;
                                    dr[Enums.DataColumn.File] = fileName;
                                    dr[Enums.DataColumn.Key] = key;

                                    value = value.Replace("\r", "\\r");
                                    value = value.Replace("\n", "\\n");

                                    dr[Enums.DataColumn.DefaultValue] = value;

                                    // Get the values from other cultures
                                    foreach (string c in cultures)
                                    {
                                        if (c.Length > 0)
                                        {
                                            if (readerList.ContainsKey(c))
                                            {
                                                string obtainedValue = this.GetValueFromResource(readerList[c], key);

                                                // If there is no value in the language add it from default
                                                // ToDo: tie this to a setting
                                                //if (obtainedValue.Length > 0)
                                                //{
                                                //    dr[c] = obtainedValue;
                                                //}
                                                //else
                                                //{
                                                //    dr[c] = dr[Enums.DataColumn.DefaultValue].ToString();
                                                //}

                                                dr[c] = obtainedValue;
                                            }
                                        }
                                    }

                                    resTable.Rows.Add(dr);

                                    counter++;
                                }
                            }
                        }
                        #endregion Read data
                    }
                    catch (Exception ex)
                    {
                        log.Error(string.Format("A problem occured reading {0}", file), ex);
                    }

                    reader.Close();

                }

                return resTable;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Loads a value from a resx file based on a key
        /// </summary>
        /// <param name="fileName">Full path to the resx file</param>
        /// <param name="resxKey">The key belonging to the value</param>
        /// <returns>The value belonging to the key</returns>
        public string GetValueFromResource(string fileName, string resxKey)
        {
            try
            {
                using (ResourceReader reader = new ResourceReader(fileName))
                {
                    return this.GetValueFromResource(reader, resxKey);
                }
            }
            catch (Exception ex)
            {
                log.Error(string.Format("A problem occured reading '{0}'", fileName), ex);
                return string.Empty;
            }
        }

        /// <summary>
        /// Loads a value from a resx file reader based on a key
        /// </summary>
        /// <param name="reader">Reference to an opened Resx reader</param>
        /// <param name="resxKey">The key belonging to the value</param>
        /// <returns>The value belonging to the key</returns>
        public string GetValueFromResource(ResourceReader reader, string resxKey)
        {
            try
            {
                // IDictionaryEnumerator ide = reader.GetEnumerator();

                foreach (DictionaryEntry de in reader)
                {
                    if (de.Value is string)
                    {
                        string key = (string)de.Key;

                        if (key.Equals(resxKey, StringComparison.OrdinalIgnoreCase))
                        {
                            return de.Value.ToString();
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                log.Error(string.Format("A problem occured reading '{0}'", reader), ex);
            }

            return string.Empty;
        }

        /// <summary>
        /// Saves data from a strict structure table to one or more resx files in a specified directory
        /// </summary>
        /// <param name="data">Table containing data in a specific format
        /// File, Key, Default value, de, es</param>
        /// <param name="savePath">The dir where the files will be saved relative to</param>
        /// <returns>True on success, else false</returns>
        public bool SaveResourceGroup(DataTable data, string savePath)
        {
            if (data == null)
            {
                log.Error("Data is null");
                return false;
            }

            // ToDo: Verify if nr column was needed
            if (!data.Columns.Contains(Enums.DataColumn.Directory) ||
                !data.Columns.Contains(Enums.DataColumn.File) ||
                !data.Columns.Contains(Enums.DataColumn.Key) ||
                !data.Columns.Contains(Enums.DataColumn.DefaultValue))
            {
                log.Error("Data does not contain necessary columns");
                return false;
            }

            try
            {
                List<string> cultures = new List<string>();

                // Extract cultures
                foreach (DataColumn dc in data.Columns)
                {
                    if (!dc.ColumnName.Equals(Enums.DataColumn.Nr, StringComparison.OrdinalIgnoreCase) &&
                        !dc.ColumnName.Equals(Enums.DataColumn.Directory, StringComparison.OrdinalIgnoreCase) &&
                        !dc.ColumnName.Equals(Enums.DataColumn.File, StringComparison.OrdinalIgnoreCase) &&
                        !dc.ColumnName.Equals(Enums.DataColumn.Key, StringComparison.OrdinalIgnoreCase) &&
                        !dc.ColumnName.Equals(Enums.DataColumn.DefaultValue, StringComparison.OrdinalIgnoreCase))
                    {
                        cultures.Add(dc.ColumnName);
                    }
                }

                // Resx writers for other cultures
                Dictionary<string, ResourceWriter> cultureSpecWriterList = null;

                // Sort the rows so records that belong to the same resx can be processed together
                DataView dv = new DataView(data, string.Empty, Enums.DataColumn.File, DataViewRowState.None);

                string actualFile = string.Empty;
                string fullPathAndFilename = string.Empty;
                string path = string.Empty;
                ResourceWriter rw = null;

                foreach (DataRow dr in dv.Table.Rows)
                {
                    if (dr[Enums.DataColumn.File].ToString().Length > 0)
                    {
                        if (!Path.Combine(dr[Enums.DataColumn.Directory].ToString(), string.Format("{0}.{1}", dr[Enums.DataColumn.File], FileExtension)).Equals(actualFile, StringComparison.OrdinalIgnoreCase))
                        {
                            if (rw != null)
                            {
                                rw.Close();
                            }

                            actualFile = Path.Combine(dr[Enums.DataColumn.Directory].ToString(), string.Format("{0}.{1}", dr[Enums.DataColumn.File], FileExtension));

                            fullPathAndFilename = Path.Combine(savePath, actualFile);
                            path = Path.Combine(savePath, dr[Enums.DataColumn.Directory].ToString());

                            // Create dir for the file if needed
                            if (!Directory.Exists(path))
                            {
                                Directory.CreateDirectory(path);
                            }

                            rw = new ResourceWriter(fullPathAndFilename);

                            // Create writers for other resources
                            if (cultureSpecWriterList != null)
                            {
                                foreach (string c in cultures)
                                {
                                    cultureSpecWriterList[c].Close();
                                }
                            }

                            cultureSpecWriterList = new Dictionary<string, ResourceWriter>();

                            foreach (string c in cultures)
                            {
                                string cultureFileName = string.Concat(FileMngr.Instance.ExtractNameFromFilename(FileMngr.Instance.ExtractFileName(fullPathAndFilename)), ".", c, FileMngr.Instance.ExtractFileExtension(fullPathAndFilename));
                                string fullCulturePathAndFilename = Path.Combine(FileMngr.Instance.ExtractFileDir(fullPathAndFilename), cultureFileName);

                                ResourceWriter crw = new ResourceWriter(fullCulturePathAndFilename);

                                cultureSpecWriterList.Add(c, crw);
                            }
                        }

                        rw.AddResource(dr[Enums.DataColumn.Key].ToString(), dr[Enums.DataColumn.DefaultValue].ToString());

                        foreach (string c in cultures)
                        {
                            cultureSpecWriterList[c].AddResource(dr[Enums.DataColumn.Key].ToString(), dr[c].ToString());
                        }
                    }
                }

                if (rw != null)
                {
                    rw.Close();
                }

                if (cultureSpecWriterList != null)
                {
                    foreach (string c in cultures)
                    {
                        cultureSpecWriterList[c].Close();
                    }
                }
            }
            catch (Exception ex)
            {
                log.Error("Error while saving resource group.", ex);
                return false;
            }

            return true;
        }

        /// <summary>
        /// Gets the key part of the full resx key
        /// ControlNameResource1.ToolTip -> ToolTip
        /// </summary>
        /// <param name="fullKey">The full key.</param>
        /// <returns>Last part of the key</returns>
        public string GetKey(string fullKey)
        {
            if (fullKey.Length > 0)
            {
                string[] parts = fullKey.Split(new char[] { '.' });

                if (parts.Length >= 2)
                {
                    return parts[1];
                }
            }

            return string.Empty;
        }
        #endregion Public Methods
    }
}